##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id$
##------------------------------------------------------------------------------
package Yasco::Filter::Keywords;

use strict;
use vars qw[@ISA];

use Data::Dumper;
use Yasco::Filter::Result;
##------------------------------------------------------------------------------
BEGIN {
	require Yasco::Filter;
	@ISA = qw[Yasco::Filter];
	%Yasco::Filter::Keywords::list = map {$_ => 0} qw(
		addClassName
		charCodeAt
		clearTimeout
		document
		elements
		extend
		fromCharCode
		hasClassName
		indexOf
		isUndefined
		length
		location
		match
		onbeforeunload
		onclick
		onsubmit
		navigator
		Prototype
		prototype
		removeClassName
		setTimeout
		String
		substr
		substring
		userAgent
		window
	);
}
##------------------------------------------------------------------------------
sub tokens
	{
		qq{DOT,ID}
	}
##------------------------------------------------------------------------------
sub new
	{
	    my $self  = shift;
	    my $options = shift;
	    $self = $self->SUPER::new($options);
		$self->{IGNORE} = {map{$_ => 1} @{ref $self->{ignore} ? $self->{ignore} : ($self->{ignore} ? [$self->{ignore}] : [])}};
		delete $self->{ignore};
		$self->{MAPPED_OBJECT} = {};
		$self->{MAPPED_MEMBER} = {};
		$self->{MAPPED_KEYS} = {};

		if ($self->{wrapper_object})
			{
				$self->{WRAPPER_NAME} = join '', map {['A'..'Z','a'..'z']->[int(52 * rand)]} 0..1;
			}

		map {$Yasco::Filter::Keywords::list{$_} = 1} split /\|/, $self->{keywords} if exists $self->{keywords};

	    $self
	}
##------------------------------------------------------------------------------
sub get_token()
	{
		undef
	}
##------------------------------------------------------------------------------
sub register_node()
	{
		my $self = shift;
		my ($o,$node) = @_;

		return if 'ID' ne $$node->op;
		return if exists $self->{IGNORE}->{$$node->left};
		return unless exists $Yasco::Filter::Keywords::list{$$node->left};
		$Yasco::Filter::Keywords::list{$$node->left}++;

		1
	}
##------------------------------------------------------------------------------
sub execute
	{
		my $self = shift;
		my ($o,$node) = @_;
		my ($usage, $name, $mapped);

		my $peek = $o->insn_stack(1);
		my $scope = \$o->{_OB_DATA}->{scopes}->[0];

		RETR_NAME: {
			if ('ID' eq $node->op)
				{
					die Dumper($node) unless $node->parent;

					##	Remembering the scope-wide use of a keyword prevents
					##	global aliasing of "Prototype" within this example:
					##
					##		alert(Prototype.foo);
					##		(function() {
					##			var Prototype = 0;
					##			alert(Prototype.foo);
					##			if (foo())
					##				{
					##					alert(Prototype.bar);
					##				}
					##		})();
					##		alert(Prototype.foo);

					if (exists $$scope->{options}->{(__PACKAGE__)})
						{
							if (exists $$scope->{options}->{(__PACKAGE__)}->{skip}->{$node->left})
								{
									$self->verbose("Keyword \"%s\" has a different use in this scope. Skipping.\n", $node->left);
									return;
								}
						}

					##	Prevent aliasing from something like
					##		var Prototype = 0;
					##	but still allow for
					##		Prototype = 0;
					##
					##	So we traverse the parent nodes looking for a "VAR" parent
					##	not more that the current scope's depth up.
					if ('LHS' eq $peek)
						{
							#print "\n\@\@\@\t".$node->op." (".$node->left.")\n";
							for (my ($depth,$p) = ($$scope->{node_depth}, $node->parent); $p && $depth; $p = $$p->parent, $depth--)
								{
									#print ">>>\t".${$p}->op."\n";
									next if 'VAR' ne $$p->op;
									$$scope->{options}->{(__PACKAGE__)} ||= {skip => {}};
									$$scope->{options}->{(__PACKAGE__)}->{skip}->{$node->left} = 1;
									return;
								}

							#print "\n\n".Dumper([$node->left,${$node->parent}->op,$o->insn_stack(1),$scope]);
						}

					return if 'KV' eq ${$node->parent}->op;
					return if 'FUNC_DECL' eq ${$node->parent}->op;
					return if 'CLOSURE' eq ${$node->parent}->op;
					$name = $node->left, last RETR_NAME
				}

			my ($p,$lRes,$rRes) = ($node);

			if ('DOT' ne ${$node->parent}->op) {
				$p = $p->left while 'DOT' eq $p->op;
				$p->{DOT_lhs} = 1;
			}

			$lRes = $node->left->reconstruct;
			$o->push_insn('DOT_RIGHT_SIDE');
			$rRes = $node->right->reconstruct;
			$o->pop_insn;

			throw Yasco::Filter::Result($lRes . $Yasco::Node::DOT_GLUE . $rRes)
				unless exists $node->right->{MAPPED};
			throw Yasco::Filter::Result($lRes . $rRes);
		}

		return unless exists $Yasco::Filter::Keywords::list{$name};

		if (!($self->{always_replace} || 0) && 1 >= $Yasco::Filter::Keywords::list{$node->left}) {
			$self->verbose("Keyword \"%s\" used %d times and does not qualify.\n", $node->left, $Yasco::Filter::Keywords::list{$node->left});
			return;
		}

		$usage = ('DOT_RIGHT_SIDE' eq $peek ? 'MEMBER' : 'OBJECT');

		if (exists $self->{'MAPPED_'.$usage}->{$name}) {
			$mapped = $self->{'MAPPED_'.$usage}->{$name};
		} else {

			##
			## Protects this:
			##
			##    function a(href) {
			##       location.href = href;
			##    }
			##
			## from becoming:
			##
			##    function v0(p1) {tH.A[tH.B]=tHC}
			##
			PROTECT_VAR: while ('OBJECT' eq $usage) {
				FOUND_VAR_IN_SCOPE: for (;;) {
					FIND_VAR_IN_SCOPE: foreach (@{$o->{_OB_DATA}->{scopes}}) {
						last FOUND_VAR_IN_SCOPE if defined($_->{functions}) && exists($_->{functions}->{$name});
						last FOUND_VAR_IN_SCOPE if defined($_->{symbols}) && exists($_->{symbols}->{$name});
						last FOUND_VAR_IN_SCOPE if defined($_->{ids}) && exists($_->{ids}->{$name});
					}

					last PROTECT_VAR;
				}

				$self->verbose("\"%s\" used as OBJECT found in a previous scope; I shall not map.\n", $name);
				return;
			}

			RETR_MAPPED: for ($mapped = 'A';;$mapped++) {
				next if exists $self->{MAPPED_KEYS}->{$mapped};

				foreach my $scope_no (0..$#{$o->{_OB_DATA}->{scopes}})
					{
						FIND_FUNC: {
							last FIND_FUNC unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{functions});
							last FIND_FUNC unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{functions}->{$mapped});
							next RETR_MAPPED;
						}

						FIND_SYM: {
							last FIND_SYM unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols});
							last FIND_SYM unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols}->{$mapped});
							next RETR_MAPPED;
						}

						FIND_ID: {
							last FIND_ID unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{ids}->{$mapped});
							next RETR_MAPPED;
						}
					}

				$self->verbose("\"%s\" not found; \"%s\" determined undeclared. Using that from now on.\n", $name, $mapped);
				$self->{MAPPED_KEYS}->{$mapped} = 1;
				$self->{'MAPPED_'.$usage}->{$name} = $mapped;
				last RETR_MAPPED;
			}
		}

		$self->verbose("Keyword \"%s\" used as %s %d times and mapped to \"%s\".\n", $node->left, $usage, $Yasco::Filter::Keywords::list{$node->left}, $mapped);

		if ($self->{wrapper_object})
			{
				$mapped = $self->{WRAPPER_NAME}.'.'.$mapped;
			}

		if ('OBJECT' eq $usage || exists $node->{DOT_lhs} || 'LHS' eq $peek) {
			throw Yasco::Filter::Result($mapped);
		} else {
			$node->{MAPPED} = 1;
			throw Yasco::Filter::Result('['.$mapped.']');
		}
	}
##------------------------------------------------------------------------------
sub verbose
	{
		my $self = shift;
		return unless $self->{verbose};
		$Yasco::Main::instance->verbose(0x20, @_);
	}
##------------------------------------------------------------------------------
sub post_process
	{
		my ($self, $o, $code) = @_;
		my (@decls);

		if ($self->{wrapper_object})
			{
				foreach my $k (keys %{$self->{MAPPED_OBJECT}})
					{
						push @decls, sprintf('%s:%s', $self->{MAPPED_OBJECT}->{$k}, $k);
					}
				foreach my $k (keys %{$self->{MAPPED_MEMBER}})
					{
						push @decls, sprintf('%s:"%s"', $self->{MAPPED_MEMBER}->{$k}, $k);
					}

				if (@decls)
					{
						$$code	= 'var ' . $self->{WRAPPER_NAME} . '={'.join($o->option(qq{newlines}) ? ",\n" : ',', @decls) . '};'
								. ($o->option(qq{newlines}) ? "\n" : '') . $$code
					}
			}
		else ## !$self->{wrapper_object}
			{
				foreach my $k (keys %{$self->{MAPPED_OBJECT}})
					{
						push @decls, sprintf('%s=%s', $self->{MAPPED_OBJECT}->{$k}, $k);
					}
				foreach my $k (keys %{$self->{MAPPED_MEMBER}})
					{
						push @decls, sprintf('%s="%s"', $self->{MAPPED_MEMBER}->{$k}, $k);
					}

				if (@decls)
					{
						$$code	= 'var ' . join($o->option(qq{newlines}) ? ",\n" : ',', @decls) . ';'
								. ($o->option(qq{newlines}) ? "\n" : '') . $$code
					}
			}
	}
##------------------------------------------------------------------------------
1